/*
Copyright 2010 Farent-Solutions Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

 */
package com.farent.solutions.plugins.aribaweb;

import com.farent.solutions.plugins.aribaweb.ui.CustomizerProviderImpl;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.Arrays;
import java.util.Collections;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.apache.tools.ant.module.api.support.ActionUtils;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.project.classpath.ProjectClassPathModifier;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.modules.java.api.common.SourceRoots;
import org.netbeans.modules.java.api.common.ant.UpdateHelper;
import org.netbeans.modules.java.api.common.ant.UpdateImplementation;
import org.netbeans.modules.java.api.common.classpath.ClassPathModifier;
import org.netbeans.modules.java.api.common.classpath.ClassPathProviderImpl;
import org.netbeans.modules.java.api.common.project.ProjectProperties;
import org.netbeans.spi.java.project.support.LookupMergerSupport;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.AuxiliaryConfiguration;
import org.netbeans.spi.project.support.ant.AntBasedProjectRegistration;
import org.netbeans.spi.project.support.ant.AntProjectHelper;
import org.netbeans.spi.project.support.ant.FilterPropertyProvider;
import org.netbeans.spi.project.support.ant.PropertyEvaluator;
import org.netbeans.spi.project.support.ant.PropertyProvider;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.netbeans.spi.project.support.ant.ReferenceHelper;
import org.netbeans.spi.project.ui.ProjectOpenedHook;
import org.netbeans.spi.project.ui.support.DefaultProjectOperations;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;

/**
 *
 * @author farouka
 */
@AntBasedProjectRegistration(type = NBawConstants.PROJECT_TYPE,
iconResource = NBawConstants.PROJECT_ICON_PATH,
sharedName = "data",
sharedNamespace = "http://www.netbeans.org/ns/aribaweb-project/1",
privateName = "data",
privateNamespace = "http://www.netbeans.org/ns/aribaweb-project-private/1")
public final class AWNetbeansProject implements Project {

    final AntProjectHelper antProjectHelper;
    private final ClassPathProviderImpl cpProvider;
    private final ClassPathModifier cpMod;
    private final ReferenceHelper refHelper;
    private final AuxiliaryConfiguration aux;
    private final PropertyEvaluator eval;
    private SourceRoots sourceRoots;
    private SourceRoots testRoots;
    private final UpdateHelper updateHelper;

    public AWNetbeansProject(AntProjectHelper helper) {
        this.antProjectHelper = helper;
        eval = createEvaluator();

        aux = helper.createAuxiliaryConfiguration();
        refHelper = new ReferenceHelper(helper, aux, eval);

        UpdateImplementation updateProject = new UpdateProjectImpl(this, helper, aux);
        this.updateHelper = new UpdateHelper(updateProject, helper);

        this.cpProvider = new ClassPathProviderImpl(this.antProjectHelper, eval, getSourceRoots(), getTestSourceRoots()); //Does not use APH to get/put properties/cfgdata
        this.cpMod = new ClassPathModifier(this, this.updateHelper, eval, refHelper, null, createClassPathModifierCallback(), null);
    }

    public PropertyEvaluator getEval() {
        return eval;
    }

    private ClassPathModifier.Callback createClassPathModifierCallback() {
        return new ClassPathModifier.Callback() {

            public String getClassPathProperty(SourceGroup sg, String type) {
                assert sg != null : "SourceGroup cannot be null";  //NOI18N
                assert type != null : "Type cannot be null";  //NOI18N
                final String[] classPathProperty = getClassPathProvider().getPropertyName(sg, type);
                if (classPathProperty == null || classPathProperty.length == 0) {
                    throw new UnsupportedOperationException("Modification of [" + sg.getRootFolder().getPath() + ", " + type + "] is not supported"); //NOI18N
                }
                return classPathProperty[0];
            }

            public String getElementName(String classpathProperty) {
                return null;
            }
        };
    }

    @Override
    public Lookup getLookup() {
//        FileEncodingQueryImplementation encodingQuery = QuerySupport.createFileEncodingQuery(e, J2SEProjectProperties.SOURCE_ENCODING);
        @SuppressWarnings("deprecation")
        Object cpe = new org.netbeans.modules.java.api.common.classpath.ClassPathExtender(
                cpMod, ProjectProperties.JAVAC_CLASSPATH, null);
        AWSources srcs = new AWSources(this, antProjectHelper, eval, getSourceRoots(), getTestSourceRoots());
        return Lookups.fixed(new Object[]{
                    AWNetbeansProject.this,
                    new Info(),
//                    new AWNetbeansLogicalView(this),
                    new AWNetbeansLogicalView(this, updateHelper, eval, refHelper),
//                    new NBawActionProvider(),
                    new NBawActionProvider( this, updateHelper),
                    new CustomizerProviderImpl(this),
                    cpe,
                    LookupMergerSupport.createClassPathProviderMerger(cpProvider),
                    srcs,
                    srcs.getSourceGroupModifierImplementation(),
                    new ProjectOpenedHookImpl()
//                    UILookupMergerSupport.createProjectOpenHookMerger(new ProjectOpenedHookImpl())
                });
    }

    private PropertyEvaluator createEvaluator() {
        PropertyEvaluator baseEval1 = PropertyUtils.sequentialPropertyEvaluator(
                antProjectHelper.getStockPropertyPreprovider(),
                antProjectHelper.getPropertyProvider(NBawConstants.CONFIG_PROPS_PATH));

        PropertyEvaluator baseEval2 = PropertyUtils.sequentialPropertyEvaluator(
                antProjectHelper.getStockPropertyPreprovider(),
                antProjectHelper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH));

        return PropertyUtils.sequentialPropertyEvaluator(
                antProjectHelper.getStockPropertyPreprovider(),
                antProjectHelper.getPropertyProvider(NBawConstants.CONFIG_PROPS_PATH),
                new ConfigPropertyProvider(baseEval1, "nbproject/private/configs", antProjectHelper), // NOI18N
                antProjectHelper.getPropertyProvider(AntProjectHelper.PRIVATE_PROPERTIES_PATH),
                antProjectHelper.getProjectLibrariesPropertyProvider(),
                PropertyUtils.userPropertiesProvider(baseEval2,
                "user.properties.file", FileUtil.toFile(getProjectDirectory())), // NOI18N
                new ConfigPropertyProvider(baseEval1, "nbproject/configs", antProjectHelper), // NOI18N
                antProjectHelper.getPropertyProvider(AntProjectHelper.PROJECT_PROPERTIES_PATH));
    }

    @Override
    public FileObject getProjectDirectory() {
        return antProjectHelper.getProjectDirectory();
    }

    /**
     * Returns the source roots of this project
     * @return project's source roots
     */
    public synchronized SourceRoots getSourceRoots() {
        if (this.sourceRoots == null) { //Local caching, no project metadata access
            this.sourceRoots = SourceRoots.create(updateHelper, eval, refHelper,
                    NBawConstants.PROJECT_CONFIGURATION_NAMESPACE, "source-roots", false, "src.{0}{1}.dir"); //NOI18N
        }
        return this.sourceRoots;
    }

    public synchronized SourceRoots getTestSourceRoots() {
        if (this.testRoots == null) { //Local caching, no project metadata access
            this.testRoots = SourceRoots.create(updateHelper, eval, refHelper,
                    NBawConstants.PROJECT_CONFIGURATION_NAMESPACE, "test-roots", true, "test.{0}{1}.dir"); //NOI18N
        }
        return this.testRoots;
    }

    public ClassPathProviderImpl getClassPathProvider() {
        return this.cpProvider;
    }

    public ClassPathModifier getProjectClassPathModifier() {
        return this.cpMod;
    }

    private static final class ConfigPropertyProvider extends FilterPropertyProvider implements PropertyChangeListener {

        private final PropertyEvaluator baseEval;
        private final String prefix;
        private final AntProjectHelper helper;

        public ConfigPropertyProvider(PropertyEvaluator baseEval, String prefix, AntProjectHelper helper) {
            super(computeDelegate(baseEval, prefix, helper));
            this.baseEval = baseEval;
            this.prefix = prefix;
            this.helper = helper;
            baseEval.addPropertyChangeListener(this);
        }

        @Override
        public void propertyChange(PropertyChangeEvent ev) {
            if (NBawConstants.PROP_CONFIG.equals(ev.getPropertyName())) {
                setDelegate(computeDelegate(baseEval, prefix, helper));
            }
        }

        private static PropertyProvider computeDelegate(PropertyEvaluator baseEval, String prefix, AntProjectHelper helper) {
            String config = baseEval.getProperty(NBawConstants.PROP_CONFIG);
            if (config != null) {
                return helper.getPropertyProvider(prefix + "/" + config + ".properties"); // NOI18N
            } else {
                return PropertyUtils.fixedPropertyProvider(Collections.<String, String>emptyMap());
            }
        }
    }

    private final class Info implements ProjectInformation {

        @Override
        public String getName() {
            return antProjectHelper.getProjectDirectory().getName();
        }

        @Override
        public String getDisplayName() {
            return getName();
        }

        @Override
        public Icon getIcon() {
            return new ImageIcon(
                    ImageUtilities.loadImage(NBawConstants.PROJECT_ICON_PATH));
        }

        @Override
        public void addPropertyChangeListener(PropertyChangeListener pcl) {
        }

        @Override
        public void removePropertyChangeListener(PropertyChangeListener pcl) {
        }

        @Override
        public Project getProject() {
            return AWNetbeansProject.this;
        }
    }

    public AntProjectHelper getAntProjectHelper() {
        return antProjectHelper;
    }

    private final class ProjectOpenedHookImpl extends ProjectOpenedHook {

        @Override
        protected void projectOpened() {
            try {
//            ProjectClassPathModifier.
                Sources sources = ProjectUtils.getSources(AWNetbeansProject.this);
//            FileObject srcRoot = sourceGroups[0].getRootFolder();
                FileObject srcRoot = sourceRoots.getRoots()[0];
                String cp = eval.getProperty("javac.classpath");
                String[] cpRoots = PropertyUtils.tokenizePath(cp);
                URI[] urls = new URI[cpRoots.length];

                for (int i = 0; i < cpRoots.length; i++) {
                    String root = cpRoots[i];
                    urls[i] = new File(root).toURI();
                }
                ProjectClassPathModifier.addRoots(urls, srcRoot, ClassPath.COMPILE);

                GlobalPathRegistry.getDefault().register(ClassPath.BOOT, cpProvider.getProjectClassPaths(ClassPath.BOOT));
                GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, cpProvider.getProjectClassPaths(ClassPath.SOURCE));
                GlobalPathRegistry.getDefault().register(ClassPath.COMPILE, cpProvider.getProjectClassPaths(ClassPath.COMPILE));
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            } catch (UnsupportedOperationException ex) {
                Exceptions.printStackTrace(ex);
            }
        }

        @Override
        protected void projectClosed() {
            // just do if the whole project was not deleted...
            if (getProjectDirectory().isValid()) {
                // Probably unnecessary, but just in case:
                try {
                    ProjectManager.getDefault().saveProject(AWNetbeansProject.this);
                } catch (IOException e) {
                    if (!AWNetbeansProject.this.getProjectDirectory().canWrite()) {
                        // #91398 - ignore, we already reported on project open.
                        // not counting with someone setting the ro flag while the project is opened.
                    } else {
                        ErrorManager.getDefault().notify(e);
                    }
                }
            }

            // unregister project's classpaths to GlobalPathRegistry
            GlobalPathRegistry.getDefault().unregister(ClassPath.BOOT, cpProvider.getProjectClassPaths(ClassPath.BOOT));
            GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, cpProvider.getProjectClassPaths(ClassPath.SOURCE));
            GlobalPathRegistry.getDefault().unregister(ClassPath.COMPILE, cpProvider.getProjectClassPaths(ClassPath.COMPILE));
        }
    }

//    private final class NBawActionProvider implements ActionProvider {
//
//        private String[] supported = new String[]{
//            ActionProvider.COMMAND_DELETE,
//            ActionProvider.COMMAND_COPY,
//            ActionProvider.COMMAND_BUILD,
//            ActionProvider.COMMAND_CLEAN,};
//
//        @Override
//        public String[] getSupportedActions() {
//            return supported;
//        }
//
//        @Override
//        public void invokeAction(String string, Lookup lookup) throws IllegalArgumentException {
//            if (string.equals(ActionProvider.COMMAND_DELETE)) {
//                DefaultProjectOperations.performDefaultDeleteOperation(AWNetbeansProject.this);
//            }
//            if (string.equals(ActionProvider.COMMAND_COPY)) {
//                DefaultProjectOperations.performDefaultCopyOperation(AWNetbeansProject.this);
//            }
//            //Here we find the Ant script and call the target we need!
//            if (string.equals(ActionProvider.COMMAND_BUILD)) {
//                try {
//                    FileObject buildImpl = antProjectHelper.getProjectDirectory().getFileObject("build.xml");
//                    ActionUtils.runTarget(buildImpl, new String[]{"launch"}, null);
//                } catch (IOException ex) {
//                    Exceptions.printStackTrace(ex);
//                }
//            }
//            if (string.equals(ActionProvider.COMMAND_CLEAN)) {
//                try {
//                    FileObject buildImpl = antProjectHelper.getProjectDirectory().getFileObject("build.xml");
//                    ActionUtils.runTarget(buildImpl, new String[]{"clean"}, null);
//                } catch (IOException ex) {
//                    Exceptions.printStackTrace(ex);
//                }
//            }
//        }
//
//        @Override
//        public boolean isActionEnabled(String command, Lookup lookup) throws IllegalArgumentException {
//            if ((command.equals(ActionProvider.COMMAND_DELETE))) {
//                return true;
//            } else if ((command.equals(ActionProvider.COMMAND_COPY))) {
//                return true;
//            } else if ((command.equals(ActionProvider.COMMAND_BUILD))) {
//                return true;
//            } else if ((command.equals(ActionProvider.COMMAND_CLEAN))) {
//                return true;
//            }else {
//                throw new IllegalArgumentException(command);
//            }
//        }
//    }
}
